package com.tangruojie.config;

import com.tangruojie.filter.LoginOncePerRequestFilter;
import com.tangruojie.handler.UnauthenticatedHandler;
import com.tangruojie.handler.UnauthorizedHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * Security 配置类
 */
@Configuration
@EnableWebSecurity //  开启 Spring Security
@EnableMethodSecurity(prePostEnabled = true) // 开启 Spring Security 的方法级安全
public class SecurityConfig {

    /**
     * 获取认证管理器
     * 如需拿到 AuthenticationManager 注入业务层，统一暴露为 Bean 更常见
     *
     * @param authenticationConfiguration
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }

    /**
     * 密码编码器
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 登录过滤器
     */
    @Autowired
    private LoginOncePerRequestFilter loginOncePerRequestFilter;

    /**
     * 未认证处理类
     */
    @Autowired
    private UnauthenticatedHandler unauthenticatedHandler;

    /**
     * 未授权处理类
     */
    @Autowired
    private UnauthorizedHandler unauthorizedHandler;

    /**
     * 配置过滤器链
     *
     * @param http
     * @return
     * @throws Exception
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable) // 跨站
                /**
                 * 光写 CorsConfig 不够，因为 Security 的过滤器链优先于 MVC，需要 http.cors() 把这套配置接入进去，否则可能会被 Security 拦截掉。
                 * 只在 Spring Security 里启用 CORS，并提供一个 CorsConfigurationSource Bean。这样 无需 再写 WebMvcConfigurer#addCorsMappings。
                 */
                .cors(Customizer.withDefaults()) // 跨域
                .sessionManagement(
                        httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer
                                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 关闭 session
                ) // session 管理
                .authorizeHttpRequests(
                        authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry
                                .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 允许所有 OPTIONS 请求
                                .requestMatchers("/login/user").permitAll() // 允许用户登录
//                                .requestMatchers("/hello").hasAuthority("admin") // 测试 URL 级错误
//                                .requestMatchers("/hello").permitAll() // 测试方法级错误
                                .anyRequest().authenticated() // 其他请求都需要认证
                )
                /**
                 * 添加异常处理
                 * URL 级权限（authorizeHttpRequests / FilterSecurityInterceptor）被拒绝时，异常在 过滤器链 内部产生，才会被 ExceptionTranslationFilter 转给你配置的
                 */
                .exceptionHandling(
                        exceptionHandling -> exceptionHandling
                                // 401 Unauthorized：（未认证）
                                .authenticationEntryPoint(unauthenticatedHandler) // 认证失败处理器
                                // 403 Forbidden：（未授权）
                                .accessDeniedHandler(unauthorizedHandler) // 访问拒绝处理器
                )
                /**
                 * 关闭 Spring Security 自带的登录/登出方式
                 * 配置项	        默认行为	                    禁用后效果	                    JWT 场景推荐
                 * .formLogin()	    内置表单登录页面	            不再跳转 /login，未登录返回 401	    ✅ 禁用
                 * .httpBasic()	    浏览器 Basic 认证弹窗	        不再走 Basic 认证，直接 401	    ✅ 禁用
                 * .logout()	    /logout 清 session+cookie	不再接管 /logout，可自写接口	    ✅ 禁用
                 */
                // 前后端分离场景不需要
                .formLogin(AbstractHttpConfigurer::disable) // Spring Security 默认的表单认证（访问 /login，返回一个 HTML 登录页）
                // 一般只在调试/接口测试用，前后端分离不需要
                .httpBasic(AbstractHttpConfigurer::disable) // 浏览器弹窗输入用户名/密码的 HTTP Basic 认证
                // JWT 模式下退出通常是 前端删除 token，所以也不需要
                .logout(AbstractHttpConfigurer::disable) // Spring Security 默认的 /logout 退出登录，清 session
                .addFilterBefore(loginOncePerRequestFilter, UsernamePasswordAuthenticationFilter.class) // 添加过滤器
                .build();
    }

}
/**
 * 1、开启并接入 CORS 到 Security 链
 * 仅写一个 CorsConfig（如注册 CorsConfigurationSource 或 CorsFilter）还不够；
 * 在 HttpSecurity 上 调用 http.cors()，让 Security 使用你的 CORS 配置；
 * 常配：放行所有 OPTIONS /** 预检请求。
 * 2、关闭 CSRF（或对 API 路径忽略）
 * JWT 是无状态，一般关闭 CSRF：http.csrf(AbstractHttpConfigurer::disable)。
 * 3、无状态 Session
 * sessionCreationPolicy(SessionCreationPolicy.STATELESS)，避免 Session 与 JWT 冲突。
 * 4、认证与授权规则
 * 至少写清：哪些路径匿名可访问（如 /login、静态资源）、其余是否需要认证、某些路径是否要角色/权限校验。
 * 5、注册 JWT 过滤器并放在合适位置
 * addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)；
 * 这样请求先走你 JWT 校验，成功后 SecurityContextHolder 才有认证信息。
 * 6、统一异常返回
 * 配置 AuthenticationEntryPoint（401 未登录）和 AccessDeniedHandler（403 无权限），返回前后端约定好的 JSON，而不是默认的跳转/HTML。
 * 7、（可选）关闭不需要的内置登录方式
 * 不用表单登录/HTTP Basic 时，关闭它们，避免被误触发。
 */